package com.own.business.pay.module.wechat.service;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.own.business.pay.common.constant.ConstantPay;
import com.own.business.pay.common.em.PayEnum;
import com.own.business.pay.common.exception.PayException;
import com.own.business.pay.core.aop.AopPayService;
import com.own.business.pay.core.base.BasePayService;
import com.own.business.pay.core.method.TradeSuccessHandlerService;
import com.own.business.pay.core.model.PayOrderModel;
import com.own.business.pay.core.model.PayResultModel;
import com.own.business.pay.core.model.RefundOrderModel;
import com.own.business.pay.core.service.BillHistoryService;
import com.own.business.pay.entity.po.BillHistory;
import com.own.business.pay.module.wechat.entity.response.WechatPrepareResponse;
import com.own.business.pay.module.wechat.property.WechatPayProperty;
import com.own.business.pay.module.wechat.util.WechatPayRequestUtil;
import com.own.business.pay.module.wechat.util.WechatPaySignatureUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;

/**
 * WechatAppPayService
 * <p>
 * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml">微信支付开发文档</a>
 *
 * @author chenxueli
 * @date 2023-08-28 10:39:32
 */
@Slf4j
@Component
@AopPayService(PayEnum.Method.WECHAT)
public class WechatAppPayService implements BasePayService {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

    @Resource
    private BillHistoryService billHistoryService;
    @Resource
    private WechatPayProperty wechatPayProperty;
    @Resource
    private WechatPayRequestUtil wechatPayRequestUtil;
    @Resource
    private List<TradeSuccessHandlerService> tradeSuccessHandlerServiceList;

    /**
     * 支出方法
     *
     * @param type  支付方式
     * @param model 支付订单
     * @return 支付凭证
     */
    @Override
    public PayResultModel pay(PayEnum.Type type, PayOrderModel model) {
        var map = new JSONObject();
        map.put("appid", wechatPayProperty.getAppId());
        map.put("mchid", wechatPayProperty.getMchId());
        map.put("description", model.getSubject());
        map.put("time_expire", null);
        map.put("out_trade_no", model.getTradeNo());
        map.put("notify_url", wechatPayProperty.getNotifyUrl());
        map.put("amount", new JSONObject().fluentPut("total", model.getTotalAmount()).fluentPut("currency", "CNY"));
        var body = JSON.toJSONString(map);
        var object = wechatPayRequestUtil.post("https://api.mch.weixin.qq.com/v3/pay/transactions/app", body);
        if (object.containsKey("prepay_id")) {
            // 处理app调起支付的所有参数
            var vo = buildPrepayInfo(object.getString("prepay_id"));
            return new PayResultModel(model.getTradeNo(), JSON.toJSONString(vo));
        }
        throw new PayException("微信支付失败");
    }

    /**
     * 收入方法（提现）
     *
     * @param model 收入订单
     */
    @Override
    public void income(PayOrderModel model) {
        throw new PayException("微信支付不支持收入该方法");
    }

    /**
     * 查询订单
     * <p>
     * <a href="https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/">查询订单</a>{out_trade_no}
     *
     * @param outTradeNo 商户订单号
     * @return 账单信息x
     */
    @Override
    public BillHistory query(String outTradeNo) {
        // 查询对应的订单是否完成
        var bill = billHistoryService.getByTradeNo(outTradeNo);
        var object = wechatPayRequestUtil.get("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "?mchid=" + wechatPayProperty.getMchId());
        return handlerPayComplete(bill, outTradeNo, object);
    }

    /**
     * 处理支付订单信息
     *
     * @param outTradeNo 商户订单号
     * @param object     微信处的支付信息
     * @return 账单信息
     */
    public BillHistory handlerPayComplete(String outTradeNo, JSONObject object) {
        return handlerPayComplete(null, outTradeNo, object);
    }

    /**
     * 处理支付订单信息
     *
     * @param bill       账单信息
     * @param outTradeNo 商户订单号
     * @param object     微信处的支付信息
     * @return 账单信息
     */
    public BillHistory handlerPayComplete(BillHistory bill, String outTradeNo, JSONObject object) {
        if (bill == null) {
            bill = billHistoryService.getByTradeNo(outTradeNo);
            if (bill == null) {
                throw new PayException("支付订单信息不存在");
            }
        }
        var status = object.getString("trade_state");
        switch (status) {
            case "SUCCESS" -> {
                bill.setStatus(ConstantPay.Status.COMPLETE_SUCCESS);
                bill.setNotifyData(object.toJSONString());
                bill.setTradeTime(LocalDateTime.parse(object.getString("success_time"), DATE_TIME_FORMATTER));
                billHistoryService.update(bill);
                // 处理支付成功的业务
                tradeSuccessHandlerServiceList.forEach(method -> method.pay(PayEnum.Method.WECHAT, outTradeNo));
            }
            // CLOSED：已关闭、REFUND：转入退款、REVOKED：已撤销（付款码支付）
            case "CLOSED", "REFUND", "REVOKED" -> {
                bill.setStatus(ConstantPay.Status.COMPLETE_CLOSE);
                bill.setNotifyData(object.toJSONString());
                bill.setTradeTime(LocalDateTime.parse(object.getString("success_time"), DATE_TIME_FORMATTER));
                billHistoryService.update(bill);
            }
            case "NOTPAY" -> throw new PayException("未支付");
            case "USERPAYING" -> throw new PayException("退款处理中");
            case "PAYERROR" -> throw new PayException("支付失败");
        }
        return bill;
    }

    /**
     * 关闭订单
     *
     * @param outTradeNo 商户订单号
     */
    @Override
    public void close(String outTradeNo) {
        var map = new HashMap<String, String>();
        map.put("mchid", wechatPayProperty.getMchId());
        map.put("out_trade_no", outTradeNo);
        var json = JSON.toJSONString(map);
        wechatPayRequestUtil.post("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "/close", json);
    }

    /**
     * 订单退款（仅提交）
     * 1、交易时间超过一年的订单无法提交退款
     * 2、微信支付退款支持单笔交易分多次退款（不超50次），多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交，请不要更换退款单号，请使用原商户退款单号
     * 3、错误或无效请求频率限制：6qps，即每秒钟异常或错误的退款申请请求不超过6次
     * 4、每个支付订单的部分退款次数不能超过50次
     * 5、如果同一个用户有多笔退款，建议分不同批次进行退款，避免并发退款导致退款失败
     * 6、申请退款接口的返回仅代表业务的受理情况，具体退款是否成功，需要通过退款查询接口获取结果
     * 7、一个月之前的订单申请退款频率限制为：5000/min
     * 8、同一笔订单多次退款的请求需相隔1分钟
     *
     * @param outTradeNo 商户订单
     * @param model      退款信息
     */
    @Override
    public void refund(String outTradeNo, RefundOrderModel model) {
        var map = new JSONObject();
        map.put("out_trade_no", outTradeNo);
        map.put("out_refund_no", model.getRefundNo());
        map.put("reason", model.getReason());
        map.put("notify_url", wechatPayProperty.getRefundNotifyUrl());
        map.put("amount", new JSONObject().fluentPut("refund", 0).fluentPut("total", model.getTotal()).fluentPut("currency", "CNY"));
        var body = JSON.toJSONString(map);
        wechatPayRequestUtil.post("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds", body);
    }

    /**
     * 查询单笔退款信息
     * <p>
     * 提交退款申请后，通过调用该接口查询退款状态。退款有一定延时，建议在提交退款申请后1分钟发起查询退款状态，一般来说零钱支付的退款5分钟内到账，银行卡支付的退款1-3个工作日到账。
     *
     * @param outTradeNo  商户订单
     * @param outRefundNo 商户退款单号
     * @return 账单信息
     */
    @Override
    public BillHistory queryRefund(String outTradeNo, String outRefundNo) {
        // 查询对应的订单是否完成
        var bill = billHistoryService.getByRefundNo(outRefundNo);
        var object = wechatPayRequestUtil.get("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/" + outRefundNo);
        return handlerRefundComplete(bill, outRefundNo, object);
    }

    /**
     * 处理退款订单信息
     *
     * @param outTradeNo 商户退款订单号
     * @param object     微信处的退款信息
     * @return 账单信息
     */
    public BillHistory handlerRefundComplete(String outTradeNo, JSONObject object) {
        return handlerRefundComplete(null, outTradeNo, object);
    }

    /**
     * 处理退款订单信息
     *
     * @param bill       账单信息
     * @param outTradeNo 商户退款订单号
     * @param object     微信处的退款信息
     * @return 账单信息
     */
    public BillHistory handlerRefundComplete(BillHistory bill, String outTradeNo, JSONObject object) {
        if (bill == null) {
            bill = billHistoryService.getByTradeNo(outTradeNo);
            if (bill == null) {
                throw new PayException("退款订单信息不存在");
            }
        }
        var status = object.getString("status");
        log.info("退款失败：{}", JSON.toJSONString(object));
        var outRefundNo = bill.getOutRefundNo();
        switch (status) {
            case "SUCCESS" -> {
                bill.setStatus(ConstantPay.Status.COMPLETE_SUCCESS);
                bill.setNotifyData(object.toJSONString());
                bill.setTradeTime(LocalDateTime.parse(object.getString("success_time"), DATE_TIME_FORMATTER));
                billHistoryService.update(bill);
                // 处理退款成功的业务
                tradeSuccessHandlerServiceList.forEach(method -> method.refund(PayEnum.Method.WECHAT, outTradeNo, outRefundNo));
                // 验证订单信息
                billHistoryService.checkRefund(outTradeNo);
            }
            case "CLOSED" -> {
                bill.setStatus(ConstantPay.Status.COMPLETE_CLOSE);
                bill.setNotifyData(object.toJSONString());
                bill.setTradeTime(LocalDateTime.parse(object.getString("success_time"), DATE_TIME_FORMATTER));
                billHistoryService.update(bill);
            }
            case "PROCESSING" -> throw new PayException("退款处理中");
            case "ABNORMAL" -> throw new PayException("退款异常");
        }
        return bill;
    }


    /**
     * 常见返回对象
     *
     * @param prepayId 预支付id
     * @return 返回对象
     */
    private WechatPrepareResponse buildPrepayInfo(String prepayId) {
        var nonce = UUID.randomUUID().toString().replace("-", "");
        var timestamp = System.currentTimeMillis() / 1000;
        // 加密
        var content = wechatPayProperty.getAppId() + "\n" + timestamp + "\n" + nonce + "\n" + prepayId + "\n";
        var signature = WechatPaySignatureUtil.encrypt(content, WechatPayProperty.PRIVATE_KEY);
        // 创建对象
        var model = new WechatPrepareResponse();
        model.setAppId(wechatPayProperty.getAppId());
        model.setMchId(wechatPayProperty.getMchId());
        model.setPackageStr("Sign=WXPay");
        model.setPrepayId(prepayId);
        model.setNonceStr(nonce);
        model.setTimestamp(timestamp);
        model.setSign(signature);
        return model;
    }

}
